
from OpenGL.GLU import *
from OpenGL.GL import *
from amt.cg.core.arcball import Arcball
from amt.cg.core.util import multiply_vector_by_matrix
import numpy

class Object3D(object):
    '''
    This abstract class represents and draws a 3D OpenGL object.
    '''

    DEFAULT_CENTER_POSITION = [0.0, 0.0, 0.0]    
    DEFAULT_COLOR = [0.27, 0.58, 0.82, 1.0]  # blue    
    DEFAULT_ROTATION_MATRIX = numpy.identity(4)
    SELECTED_COLOR = [1.0, 0.67, 0.67, 1.0]  # light red
    
    def __init__(self, center_position=None, camera=None, 
                 rotation_matrix=None, color=None, pickable=False):
        '''
        Constructor method. Initializes the object attributes.
        
        camera - a reference to the scene's camera
        center_position - the position of the object's center 
        rotation_matrix - a 4x4 matrix that contains the object's rotation
        color - a list that represents the RGB color of this object
        pickable - specifies whether the object will be pickable
        '''
        if center_position is not None:
            self.center_position = list(center_position)
        else:
            self.center_position = Object3D.DEFAULT_CENTER_POSITION
        
        if rotation_matrix is not None:
            self.rotation_matrix = list(rotation_matrix)
        else:
            self.rotation_matrix = Object3D.DEFAULT_ROTATION_MATRIX

        if color is not None:
            self.color = list(color)
        else:
            self.color = Object3D.DEFAULT_COLOR
            
        if pickable:
            self.pickable = True
            self.selected = False
        
        self.camera = camera
        self.arcball_radius = 0.0
         
    def draw(self, mode=GL_RENDER, name=None):
        '''
        Draws this 3D object to the screen.
        '''
        glPushMatrix()
        
        self._fill()
        
        glTranslate(*self.center_position)
        glMultMatrixf(self.rotation_matrix)  # rotation
        
        if self.pickable and mode == GL_SELECT:
            glLoadName(name)
            
        self._draw_geometry();
        
        glPopMatrix()
       
    def translate(self, x, y, z, w=1):
        '''
        Translates this 3D object with the specified homogeneous
        coordinates.
        If w = 1, the coordinates describe a point that will be the
        new position of the object's center.
        If w = 0, the coordinates describe a vector that will be
        added to the position of the object's center.
        This transformation will be applied when the draw method 
        is called.
        '''
        if w == 1:
            self.center_position = [x, y, z]
        elif w == 0:            
            for i in range(3):
                self.center_position[i] += (x, y, z)[i]
        
        self.arcball.real_center = self.center_position
        
    def scale(self, x_scale, y_scale, z_scale):
        '''
        Scales this 3D object along the three axes.
        This transformation will be applied when the draw method 
        is called.
        
        x_scale - the amount this object will be scaled along the X axis.
        y_scale - the amount this object will be scaled along the Y axis.
        z_scale - the amount this object will be scaled along the Z axis.
        '''
        pass
    
    def _draw_geometry(self):
        '''
        Draws the 3D object's vertices.
        This method is abstract and must be implemented in the subclass.
        '''
        pass
    
    def _fill(self):
        '''
        Defines how the 3D object will be colored.
        '''
        color = self.color
        if self.selected:
            color = Object3D.SELECTED_COLOR
            
        glMaterial(GL_FRONT, GL_DIFFUSE, color)        
        
    def right_button_click(self, x, y):
        '''
        This method is called when the right mouse button is clicked.
        It defines the initial point of the arcball sphere. 
        '''
        self.arcball.set_initial_point(x, y)        
 
    def rotate(self, x, y):
        '''
        This method rotates the object. 
        ''' 
        self.arcball.set_final_point(x, y)
        
        (rotation_vector, angle) = self.arcball.rotate()
        
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        glRotate(angle, *rotation_vector)
        glMultMatrixf(self.rotation_matrix)
        self.rotation_matrix = glGetDouble(GL_MODELVIEW_MATRIX)
        glPopMatrix()
    
    def update_arcball_points(self):
        for i in range(3):
            self.arcball.initial_point[i] = self.arcball.final_point[i]

    def select(self):
        if self.pickable and not self.selected:
            self.selected = True
            
    def deselect(self):
        if self.pickable and self.selected:
            self.selected = False
